package com.pig.openg1.render;


import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.os.SystemClock;
import android.util.Log;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class GraphThreeRender implements GLSurfaceView.Renderer {

    private final String TAG = GraphThreeRender.class.getSimpleName();

    private String mVertexShaderSource = "" +
            "uniform mat4 u_MVPMatrix;    \n" +
            "uniform mat4 u_MVMatrix;     \n" +
            "uniform vec3 u_LightPosition;\n " +
            "attribute vec4 a_Position;   \n" +
            "attribute vec4 a_Color;      \n" +
            "attribute vec3 a_Normal;     \n" +
            "varying vec3 v_Position;     \n" +
            "varying vec4 v_Color;        \n" +
            "varying vec3 v_Normal;       \n" +
            "void main()                  \n" +
            "{" +
            "v_Position = vec3(u_MVMatrix * a_Position); \n" +
            "v_Color = a_Color;           \n" +
            "v_Normal = vec3(u_MVMatrix * vec4(a_Normal,0.0)); \n" +
            "gl_Position = u_MVPMatrix * a_Position;\n" +
            "}";



    private String mFragShadderSource = "" +
            "precision mediump float; \n" +
            "uniform vec3 u_LightPosition; \n" +
            "varying vec3 v_Position; \n " +
            "varying vec4 v_Color; \n " +
            "varying vec3 v_Normal; \n" +
            "void main()" +
            "{" +
            "float distance = length(u_LightPosition - v_Position);\n" +
            "vec3 mLightVector = normalize(u_LightPosition - v_Position); \n" +
            "float diffuse = max(dot(v_Normal,mLightVector),0.1); \n" +
            "diffuse = diffuse * (1.0/(1.0 + (0.25 * distance * distance))); \n " +
            "gl_FragColor = v_Color * diffuse; \n" +
            "}";

    private String mPointVertexShaderSource =
            "uniform mat4 u_MVPMatrix; \n" +
            "attribute vec4 a_Position; \n" +
            "void main()" +
            "{" +
            "gl_Position = u_MVPMatrix * a_Position; \n" +
            "gl_PointSize = 5.0; \n" +
            "}";

    private String mPointFragmentShaderSource =
            "precision mediump float; \n" +
            "void main()" +
            "{" +
            "gl_FragColor = vec4(1.0,1.0,1.0,1.0); \n" +
            "}";

    private float[] mModelMatrix = new float[16];
    private float[] mViewMatrix = new float[16];
    private float[] mProjectionMatrix = new float[16];
    private float[] mMVPMatrix = new float[16];
    private float[] mLightModelMatrix = new float[16];

    private FloatBuffer mCubePosition;
    private FloatBuffer mCubeColor;
    private FloatBuffer mCubeNormal;

    private int mMVPMatrixHandle;
    private int mMViewMatrixHandle;
    private int mPositionHandle;
    private int mColorHandle;
    private int mNormalHandle;

    private int mLightPositionHandle;

    private final int mBytePerFloat = 4;
    private final int mPositionDataSize = 3;
    private final int mColorDataSize = 4;
    private final int mNormalDataSize = 3;

    private float[] mLightPositionModel = new float[]{0.0f,0.0f,0.0f,1.0f};
    private float[] mLightPositionWorld = new float[4];
    private float[] mLightPositionEye = new float[4];

    private int mCubeProgramHandle;
    private int mPointProgramHandle;

    public GraphThreeRender()
    {
        float[] mCubePositionData={
            // Front face
            -1.0f, 1.0f, 1.0f,
            -1.0f, -1.0f, 1.0f,
            1.0f, 1.0f, 1.0f,
            -1.0f, -1.0f, 1.0f,
            1.0f, -1.0f, 1.0f,
            1.0f, 1.0f, 1.0f,

            // Right face
            1.0f, 1.0f, 1.0f,
            1.0f, -1.0f, 1.0f,
            1.0f, 1.0f, -1.0f,
            1.0f, -1.0f, 1.0f,
            1.0f, -1.0f, -1.0f,
            1.0f, 1.0f, -1.0f,

            // Back face
            1.0f, 1.0f, -1.0f,
            1.0f, -1.0f, -1.0f,
            -1.0f, 1.0f, -1.0f,
            1.0f, -1.0f, -1.0f,
            -1.0f, -1.0f, -1.0f,
            -1.0f, 1.0f, -1.0f,

            // Left face
            -1.0f, 1.0f, -1.0f,
            -1.0f, -1.0f, -1.0f,
            -1.0f, 1.0f, 1.0f,
            -1.0f, -1.0f, -1.0f,
            -1.0f, -1.0f, 1.0f,
            -1.0f, 1.0f, 1.0f,

            // Top face
            -1.0f, 1.0f, -1.0f,
            -1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, -1.0f,
            -1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, -1.0f,

            // Bottom face
            1.0f, -1.0f, -1.0f,
            1.0f, -1.0f, 1.0f,
            -1.0f, -1.0f, -1.0f,
            1.0f, -1.0f, 1.0f,
            -1.0f, -1.0f, 1.0f,
            -1.0f, -1.0f, -1.0f
        };

        float[] mCubeColorData = {
            // Front face (red)
            1.0f, 0.0f, 0.0f, 1.0f,
            1.0f, 0.0f, 0.0f, 1.0f,
            1.0f, 0.0f, 0.0f, 1.0f,
            1.0f, 0.0f, 0.0f, 1.0f,
            1.0f, 0.0f, 0.0f, 1.0f,
            1.0f, 0.0f, 0.0f, 1.0f,

            // Right face (green)
            0.0f, 1.0f, 0.0f, 1.0f,
            0.0f, 1.0f, 0.0f, 1.0f,
            0.0f, 1.0f, 0.0f, 1.0f,
            0.0f, 1.0f, 0.0f, 1.0f,
            0.0f, 1.0f, 0.0f, 1.0f,
            0.0f, 1.0f, 0.0f, 1.0f,

            // Back face (blue)
            0.0f, 0.0f, 1.0f, 1.0f,
            0.0f, 0.0f, 1.0f, 1.0f,
            0.0f, 0.0f, 1.0f, 1.0f,
            0.0f, 0.0f, 1.0f, 1.0f,
            0.0f, 0.0f, 1.0f, 1.0f,
            0.0f, 0.0f, 1.0f, 1.0f,

            // Left face (yellow)
            1.0f, 1.0f, 0.0f, 1.0f,
            1.0f, 1.0f, 0.0f, 1.0f,
            1.0f, 1.0f, 0.0f, 1.0f,
            1.0f, 1.0f, 0.0f, 1.0f,
            1.0f, 1.0f, 0.0f, 1.0f,
            1.0f, 1.0f, 0.0f, 1.0f,

            // Top face (cyan)
            0.0f, 1.0f, 1.0f, 1.0f,
            0.0f, 1.0f, 1.0f, 1.0f,
            0.0f, 1.0f, 1.0f, 1.0f,
            0.0f, 1.0f, 1.0f, 1.0f,
            0.0f, 1.0f, 1.0f, 1.0f,
            0.0f, 1.0f, 1.0f, 1.0f,

            // Bottom face (magenta)
            1.0f, 0.0f, 1.0f, 1.0f,
            1.0f, 0.0f, 1.0f, 1.0f,
            1.0f, 0.0f, 1.0f, 1.0f,
            1.0f, 0.0f, 1.0f, 1.0f,
            1.0f, 0.0f, 1.0f, 1.0f,
            1.0f, 0.0f, 1.0f, 1.0f
        };

        float[] mCubeNormalData ={
            // Front face
            0.0f, 0.0f, 1.0f,
            0.0f, 0.0f, 1.0f,
            0.0f, 0.0f, 1.0f,
            0.0f, 0.0f, 1.0f,
            0.0f, 0.0f, 1.0f,
            0.0f, 0.0f, 1.0f,

            // Right face
            1.0f, 0.0f, 0.0f,
            1.0f, 0.0f, 0.0f,
            1.0f, 0.0f, 0.0f,
            1.0f, 0.0f, 0.0f,
            1.0f, 0.0f, 0.0f,
            1.0f, 0.0f, 0.0f,

            // Back face
            0.0f, 0.0f, -1.0f,
            0.0f, 0.0f, -1.0f,
            0.0f, 0.0f, -1.0f,
            0.0f, 0.0f, -1.0f,
            0.0f, 0.0f, -1.0f,
            0.0f, 0.0f, -1.0f,

            // Left face
            -1.0f, 0.0f, 0.0f,
            -1.0f, 0.0f, 0.0f,
            -1.0f, 0.0f, 0.0f,
            -1.0f, 0.0f, 0.0f,
            -1.0f, 0.0f, 0.0f,
            -1.0f, 0.0f, 0.0f,

            // Top face
            0.0f, 1.0f, 0.0f,
            0.0f, 1.0f, 0.0f,
            0.0f, 1.0f, 0.0f,
            0.0f, 1.0f, 0.0f,
            0.0f, 1.0f, 0.0f,
            0.0f, 1.0f, 0.0f,

            // Bottom face
            0.0f, -1.0f, 0.0f,
            0.0f, -1.0f, 0.0f,
            0.0f, -1.0f, 0.0f,
            0.0f, -1.0f, 0.0f,
            0.0f, -1.0f, 0.0f,
            0.0f, -1.0f, 0.0f
        };

        mCubePosition = ByteBuffer.allocateDirect(mCubePositionData.length * mBytePerFloat)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mCubePosition.put(mCubePositionData).position(0);

        mCubeColor = ByteBuffer.allocateDirect(mCubeColorData.length * mBytePerFloat)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mCubeColor.put(mCubeColorData).position(0);

        mCubeNormal = ByteBuffer.allocateDirect(mCubeNormalData.length * mBytePerFloat)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mCubeNormal.put(mCubeNormalData).position(0);



    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {

        GLES20.glClearColor(0.0f,0.0f,0.0f,0.0f);
        GLES20.glEnable(GLES20.GL_CULL_FACE);
        GLES20.glEnable(GLES20.GL_DEPTH_TEST);

        Matrix.setLookAtM(mViewMatrix,0,0.0f,0.0f,-0.5f,
                0.0f,0.0f,-5.0f,
                0.0f,1.0f,0.0f);

        //加载片元信息
        int mVertexShader = loadShader(GLES20.GL_VERTEX_SHADER,mVertexShaderSource);
        int mFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,mFragShadderSource);
        if(mVertexShader == 0 )
            throw new RuntimeException("Error create vertex shader !");
        if(mFragmentShader == 0 )
            throw new RuntimeException("Error create fragment shader !");
        mCubeProgramHandle = GLES20.glCreateProgram();
        if( mCubeProgramHandle == 0 )
            throw new RuntimeException("Error crate cube program !");
        GLES20.glAttachShader(mCubeProgramHandle,mVertexShader);
        GLES20.glAttachShader(mCubeProgramHandle,mFragmentShader);
        //
        GLES20.glBindAttribLocation(mCubeProgramHandle,0,"a_Position");
        GLES20.glBindAttribLocation(mCubeProgramHandle,1,"a_Color");
        GLES20.glBindAttribLocation(mCubeProgramHandle,2,"a_Normal");
        GLES20.glLinkProgram(mCubeProgramHandle);

        int[] linkStatus = new int[1];
        GLES20.glGetProgramiv(mCubeProgramHandle,GLES20.GL_LINK_STATUS,linkStatus,0);
        if(linkStatus[0] == GLES20.GL_FALSE)
        {
            Log.e(TAG,GLES20.glGetProgramInfoLog(mCubeProgramHandle));
            GLES20.glDeleteProgram(mCubeProgramHandle);
            mCubeProgramHandle = 0 ;
        }

        int mPointVertexShader = loadShader(GLES20.GL_VERTEX_SHADER,mPointVertexShaderSource);
        int mPointFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,mPointFragmentShaderSource);
        if(mPointVertexShader == 0 )
            throw new RuntimeException("Error create the point of vertex shader !");
        if(mPointFragmentShader == 0 )
            throw new RuntimeException("Error create the point of fragment shader !");
        mPointProgramHandle = GLES20.glCreateProgram();
        if(mPointProgramHandle == 0 )
            throw new RuntimeException("Error create the point of program !");
        GLES20.glAttachShader(mPointProgramHandle,mPointVertexShader);
        GLES20.glAttachShader(mPointProgramHandle,mPointFragmentShader);
        GLES20.glBindAttribLocation(mPointProgramHandle,0,"a_Position");
        GLES20.glLinkProgram(mPointProgramHandle);
        int[] pointLinkStatus = new int[1];
        GLES20.glGetProgramiv(mPointProgramHandle,GLES20.GL_LINK_STATUS,pointLinkStatus,0);
        if(pointLinkStatus[0] == GLES20.GL_FALSE)
        {
            Log.e(TAG,GLES20.glGetProgramInfoLog(mPointProgramHandle));
            GLES20.glDeleteProgram(mPointProgramHandle);
            mPointProgramHandle = 0 ;
        }


    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {

        GLES20.glViewport(0,0,width,height);
        float ratio = (float)width / height;
        Matrix.frustumM(mProjectionMatrix,0
                ,-ratio,ratio,-1.0f,1.0f,1.0f,10.0f);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        long time = SystemClock.uptimeMillis() % 10000L;
        float angleDegree = (360.0f / 10000.0f) * ((int) time);
//        long time = SystemClock.uptimeMillis() % 10000L;
//        float angleDegree = (360.0f / 10000.0f) * ((int) time);
        GLES20.glUseProgram(mCubeProgramHandle);

        mMVPMatrixHandle = GLES20.glGetUniformLocation(mCubeProgramHandle,"u_MVPMatrix");
        mMViewMatrixHandle = GLES20.glGetUniformLocation(mCubeProgramHandle,"u_MVMatrix");
        mPositionHandle = GLES20.glGetAttribLocation(mCubeProgramHandle,"a_Position");
        mColorHandle = GLES20.glGetAttribLocation(mCubeProgramHandle,"a_Color");
        mNormalHandle = GLES20.glGetAttribLocation(mCubeProgramHandle,"a_Normal");
        mLightPositionHandle = GLES20.glGetUniformLocation(mCubeProgramHandle,"u_LightPosition");

        Matrix.setIdentityM(mLightModelMatrix,0);
        Matrix.translateM(mLightModelMatrix,0,0.0f,0.0f,-5.0f);
        Matrix.rotateM(mLightModelMatrix,0,angleDegree,0.0f,1.0f,0.0f);
        Matrix.translateM(mLightModelMatrix,0,0.0f,0.0f,2.0f);

        //矩阵变化
        Matrix.multiplyMV(mLightPositionWorld,0, mLightModelMatrix,
                0,mLightPositionModel,0);

        Matrix.multiplyMV(mLightPositionEye,0,mViewMatrix,0,mLightPositionWorld,0);

        //绘制
        Matrix.setIdentityM(mModelMatrix,0);
        Matrix.translateM(mModelMatrix,0,4.0f,0.0f,-7.0f);
        Matrix.rotateM(mModelMatrix,0,angleDegree,1.0f,0.0f,0.0f);

        drawCube();


        Matrix.setIdentityM(mModelMatrix, 0);
        Matrix.translateM(mModelMatrix, 0, -4.0f, 0.0f, -7.0f);
        Matrix.rotateM(mModelMatrix, 0, angleDegree, 0.0f, 1.0f, 0.0f);
        drawCube();

        Matrix.setIdentityM(mModelMatrix, 0);
        Matrix.translateM(mModelMatrix, 0, 0.0f, 4.0f, -7.0f);
        Matrix.rotateM(mModelMatrix, 0, angleDegree, 0.0f, 0.0f, 1.0f);
        drawCube();

        Matrix.setIdentityM(mModelMatrix, 0);
        Matrix.translateM(mModelMatrix, 0, 0.0f, -4.0f, -7.0f);
        drawCube();

        Matrix.setIdentityM(mModelMatrix, 0);
        Matrix.translateM(mModelMatrix, 0, 0.0f, 0.0f, -5.0f);
        Matrix.rotateM(mModelMatrix, 0, angleDegree, 1.0f, 1.0f, 0.0f);
        drawCube();

        GLES20.glUseProgram(mPointProgramHandle);
        drawLight();

    }

    private void drawCube() {

        mCubePosition.position(0);
        GLES20.glVertexAttribPointer(mPositionHandle,mPositionDataSize,GLES20.GL_FLOAT,false,
                0,mCubePosition);
        GLES20.glEnableVertexAttribArray(mPositionHandle);

        mCubeNormal.position(0);
        GLES20.glVertexAttribPointer(mNormalHandle,mNormalDataSize,GLES20.GL_FLOAT,false,
                0,mCubeNormal);
        GLES20.glEnableVertexAttribArray(mNormalHandle);

        mCubeColor.position(0);
        GLES20.glVertexAttribPointer(mColorHandle,mColorDataSize,GLES20.GL_FLOAT,false,
                0,mColorHandle);
        GLES20.glEnableVertexAttribArray(mColorHandle);

        Matrix.multiplyMM(mMVPMatrix,0,mViewMatrix,0,mModelMatrix,0);
        Matrix.multiplyMM(mMVPMatrix,0,mProjectionMatrix,0,mMVPMatrix,0);

        GLES20.glUniformMatrix4fv(mMViewMatrixHandle,1,false,mMVPMatrix,0);
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle,1,false,mMVPMatrix,0);
        GLES20.glUniform3f(mLightPositionHandle,mLightPositionEye[0],mLightPositionEye[1],mLightPositionEye[2]);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES,0,36);

    }

    private void drawLight() {

        int mPointMVPMatrixHandle = GLES20.glGetUniformLocation(mPointProgramHandle,"u_MVPMatrix");
        int mPointPositionHandle = GLES20.glGetAttribLocation(mPointProgramHandle,"a_Position");
        GLES20.glVertexAttrib3f(mPointPositionHandle,mLightPositionModel[0],mLightPositionModel[1],mLightPositionModel[2]);
        GLES20.glDisableVertexAttribArray(mPointPositionHandle);

        Matrix.multiplyMM(mMVPMatrix,0,mViewMatrix,0,mLightModelMatrix,0);
        Matrix.multiplyMM(mMVPMatrix,0,mProjectionMatrix,0,mMVPMatrix,0);
        GLES20.glUniformMatrix4fv(mPointMVPMatrixHandle,1,false,mMVPMatrix,0);
        GLES20.glDrawArrays(GLES20.GL_POINTS,0,1);
    }


    private int loadShader(int type,String source)
    {
        int shader = GLES20.glCreateShader(type);
        if(shader == 0 )
            throw new RuntimeException("Error create shader");
        GLES20.glShaderSource(shader,source);
        GLES20.glCompileShader(shader);
        int[] status = new int[1];
        GLES20.glGetShaderiv(shader,GLES20.GL_COMPILE_STATUS,status,0);
        if(status[0] == GLES20.GL_FALSE)
        {
            Log.e(TAG,"加载失败：->"+ source+ "\n"+ GLES20.glGetShaderInfoLog(shader));
            GLES20.glDeleteShader(shader);
            shader = 0 ;
        }
        return shader;
    }
}
